#!/usr/bin/env python
#-*- coding:utf-8 -*-
import re, sys, os
from random import randint

class JDRException(Exception):
    pass
    
class JDR:
    def __init__(self, expr, im=True):
        self.im = im
        if im:
            self._output = ["/me lance " + expr]
        else:
            self._output = ["Jet: " + expr]
        self.expr, self.carac, self.repeat = self.split_expr(expr)
        self.validate_syntax()
        self.process()
        
    def split_expr(self, expr):
        carac = None
        repeat = 1
        if "," in expr:
            try:
                s = expr.split(",")
                repeat = int(s[1].strip())
                expr = s[0]
                if repeat > 10000:
                    raise JDRException("Cannot repeat a roll more than 10000 times (to prevent DoS)")
            except (IndexError, ValueError):
                raise JDRException("Invalid syntax (bad repeat specifier) ")
        if ":" in expr:
            try:
                s = expr.split(":")
                carac = s[1].strip()
                expr = s[0].strip()
                if carac.isdigit():
                    carac = int(carac)
                else:
                    carac = JDR(carac, im=False).result
                    self._output.append("Carac = %i" % carac)
            except (IndexError, ValueError):
                raise JDRException("Invalid syntax (bad carac specifier) ")
        return expr.lower(), carac, repeat
    
    def validate_syntax(self):
        if re.search(r"[^0-9+*%/()\sdDrR-]", self.expr):
            raise JDRException("Invalid syntax (unexpected chars)")
        
    def process_dices(self, expr):
        return re.sub(r"\d+d\d+r?", self.roll, expr)

    def process(self):
        nb_success = nb_fail = nb_success_crit = nb_fail_crit = avg = 0
        for i in xrange(self.repeat):
            r = r_saved = self.process_dices(self.expr)
            while any(c in r for c in "dDrR"):
                r = re.sub(r"\([^()]+\)", self.eval_group, r)
                if r == r_saved:
                    raise JDRException("Invalid syntax ('d' or 'r' symbol found in the wrong place)")
                r = r_saved = self.process_dices(r)
            try:
                r = eval(r)
                self.result = r
            except SyntaxError:
                if "(" in r or ")" in r:
                    raise JDRException("Invalid syntax (probably unbalanced parentheses)")
                raise JDRException("Invalid syntax (probably an operator with a missing operand)")
            except ZeroDivisionError:
                raise JDRException("Invalid syntax (division by zero)")
            if any(x in self.expr for x in ["+","-","*","/","%"]):
                self._output.append("Total = %i" % r)
            crit = False
            if self.expr == "1d100":
                if r < 6:
                    self._output.append("Réussite critique !")
                    nb_success_crit += 1
                    nb_success += 1
                    crit = True
                elif r > 95:
                    self._output.append("Échec critique !")
                    nb_fail_crit += 1
                    nb_fail += 1
                    crit = True
            if self.carac:
                diff = self.carac - r
                avg += diff
                if not crit:
                    if diff >= 0:
                        m = "Passe de %i :)" % diff
                        nb_success += 1
                    else:
                        m = "Rate de %i :(" % abs(diff)
                        nb_fail += 1
                    self._output.append(m)
        if self.carac and self.repeat > 1:
            avg = int(round(float(avg)/self.repeat))
            self.result = avg
            if avg >= 0:
                m = "passe de %i !" % avg
            else:
                m = "rate de %i !" % abs(avg)
            self._output.append("Statistiques:\n\t%i %s\n\t%i %s (dont %i %s)\n\t%i %s (dont %i %s)\n\tMoyenne: %s" % (
                self.repeat, self.plural(self.repeat, "jet"),
                nb_success, self.plural(nb_success, "réussite"),
                nb_success_crit, self.plural(nb_success_crit, "réussite critique"),
                nb_fail, self.plural(nb_fail, "échec"),
                nb_fail_crit, self.plural(nb_fail_crit, "échec critique"),
                m))
    
    def eval_group(self, matchgroup):
        group = matchgroup.group(0)
        try:
            r = eval(group)
        except (SyntaxError, NameError):
            return group
        return str(r)
        
    def roll(self, matchgroup): 
        dice = matchgroup.group(0)
        if type(dice) == type(u""):
            dice = dice.encode("utf8")
        temp_output = ["[%s]" % dice]
        reroll = False
        if dice.endswith("r"):
            reroll = True
            dice = dice[:-1]
        nbroll, dicetype = [ int(x) for x in dice.split("d")]
        if dicetype == 0:
            raise JDRException("Cannot roll a dice with 0 faces")
        if nbroll >= 50000:
            raise JDRException("Cannot roll more than 50 000 dice at the same time (in order to prevent DoS)")
        result = 0
        for i in xrange(nbroll):
            r = randint(1, dicetype)
            result += r
            temp_output.append(str(r))
            if reroll and r == dicetype:
                r = randint(1, dicetype)
                result += r
                temp_output.append("(%i)" %r)
        if len(temp_output) > 2:
            temp_output.append("= %i" %result)
        self._output.append(" ".join(temp_output))
        return str(result)
    
    def plural(self, nb, s):
        if nb < 2:
            return s
        return " ".join(x+"s" for x in s.split(" "))
            
    @property
    def output(self):
        if len(self._output)>1:
            return "\n".join(self._output) + "\n"

class GUI:
    def __init__(self):
        import Tkinter
        self.root = Tkinter.Tk()
        self.root.title('JDR')
        self.bottomframe = Tkinter.Frame(self.root)
        self.txt = Tkinter.Entry(self.root, width=30)
        self.label = Tkinter.Label(self.root, text="Dice : ")
        self.button = Tkinter.Button(self.root, text=" Roll ")
        self.result = Tkinter.Text(self.bottomframe, width=40, height=10)
        self.scroll = Tkinter.Scrollbar(self.bottomframe)
        self.bottomframe.pack(side = Tkinter.BOTTOM)
        self.label.pack(side = Tkinter.LEFT)
        self.txt.pack(side = Tkinter.LEFT)
        self.button.pack(side = Tkinter.RIGHT)
        self.result.pack(side = Tkinter.LEFT, fill = Tkinter.Y)
        self.scroll.pack(side = Tkinter.RIGHT, fill = Tkinter.Y)
        self.scroll.config(command=self.result.yview)
        self.result.config(yscrollcommand = self.scroll.set)
        for event in ["<Return>","<KP_Enter>"]:
            self.txt.bind(event, self.roll)
        self.button.bind("<Button-1>", self.roll)
        self.txt.focus_set()
        self.launch()
    
    def roll(self, event):
        import Tkinter
        try:
            self.result.delete(1.0, Tkinter.END)
        except Tkinter.TclError as e:
            print e
        try:
            jdr = JDR(self.txt.get(), im=False)
            self.result.insert(Tkinter.INSERT, jdr.output)
        except JDRException as e:
            self.result.insert(Tkinter.INSERT, e)
        self.txt.delete(0, Tkinter.END)

    def launch(self):
        self.root.mainloop()
    
def main():
    if (len(sys.argv) == 1 and os.name == "nt") or \
        (len(sys.argv) == 2 and sys.argv[1] == "-g"):
        gui = GUI()
    else:
        try:
            jdr = JDR(sys.argv[1])
            o = jdr.output 
            if o:
                print o
        except JDRException as e:
            print e
        
if __name__ == '__main__':
    main()
